import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;


public class MorphImg extends JPanel {
    static int THUMB_MAX_WIDTH = 280;
    static int THUMB_MAX_HEIGHT = 210;
    static int MORPHIMAGE_MAX_WIDTH = 400;
    static int MORPHIMAGE_MAX_HEIGHT = 300;

    static boolean parallelMode = false;
    static boolean displayOff = false;

    
    BufferedImage fromThumb;
    BufferedImage toThumb;
    BufferedImage fromImage;
    BufferedImage toImage;
    BufferedImage morphImage;

    JLabel timerLabel;
    JLabel timerLabelSeconds;
    JLabel morphLabel;
    ImageIcon morphIcon;
    Dimension morphImageSize;

    long time0;
    static long elapsedTime;
    long timeSub0;
    long timeSub;

    public MorphImg(BufferedImage fromImage, BufferedImage toImage) {
        // Crop images to make them the same size
        if (fromImage.getHeight() != toImage.getHeight() || fromImage.getWidth() != toImage.getWidth()){
            int width = Math.min(fromImage.getWidth(), toImage.getWidth());
            int height = Math.min(fromImage.getHeight(), toImage.getHeight());
            toImage = toImage.getSubimage(0, 0, width, height);
            fromImage = fromImage.getSubimage(0, 0, width, height);
        }

        this.fromImage = fromImage;
        this.toImage = toImage;
        
        if(!displayOff){
	        // create thumbnails
	        morphImageSize =  ImageUtils.determineSize(fromImage.getWidth(), fromImage.getHeight(), MORPHIMAGE_MAX_WIDTH, MORPHIMAGE_MAX_HEIGHT);
	        Dimension thumbSize = ImageUtils.determineSize(fromImage.getWidth(), fromImage.getHeight(), THUMB_MAX_WIDTH, THUMB_MAX_HEIGHT);
	        this.fromThumb = ImageUtils.getScaledInstance(fromImage, thumbSize.width, thumbSize.height, RenderingHints.VALUE_INTERPOLATION_BILINEAR, true);
	        this.toThumb = ImageUtils.getScaledInstance(toImage, thumbSize.width, thumbSize.height, RenderingHints.VALUE_INTERPOLATION_BILINEAR, true);
	
	        this.createLayout();
        }
    }

    private void createLayout() {
        this.setLayout(new BorderLayout());

        //Display the from and to images
        Container topContainer = new JPanel();
        topContainer.setLayout(new FlowLayout(FlowLayout.CENTER));
        Container btmContainer = new JPanel();
        btmContainer.setLayout(new FlowLayout(FlowLayout.CENTER));
        ImageIcon fromIcon = new ImageIcon(fromThumb);
        JLabel fromLabel = new JLabel("");
        fromLabel.setIcon(fromIcon);
        fromLabel.setBorder(BorderFactory.createTitledBorder("Original image"));
        topContainer.add(fromLabel);


        ImageIcon toIcon = new ImageIcon(toThumb);
        JLabel toLabel = new JLabel("");
        toLabel.setIcon(toIcon);
        toLabel.setBorder(BorderFactory.createTitledBorder("Target image"));
        topContainer.add(toLabel);

        this.add(topContainer, BorderLayout.NORTH);

        //Display the image being changed

        //morphIcon = new ImageIcon(morphImage);
        morphLabel = new JLabel();
        //morphLabel.setIcon(morphIcon);
        morphLabel.setBorder(BorderFactory.createTitledBorder("Morphing progress"));
        btmContainer.add(morphLabel);
        this.add(btmContainer, BorderLayout.CENTER);

        Container timerContainer = new JPanel();
        timerLabel = new JLabel("Time taken: ");
        timerContainer.add(timerLabel);
        timerLabelSeconds = new JLabel("0 ms");
        timerContainer.add(timerLabelSeconds);
        this.add(timerContainer, BorderLayout.SOUTH);

    }

    public  BufferedImage morph() {

    	Morph.MorphInit(fromImage, toImage, this);
    	
    	//based on the input argument, one of them will be called
    	if(parallelMode)
    		this.morphImage = Morph.parallelDoMorph();
    	else 
    		this.morphImage = Morph.serialDoMorph();
    	
        updateMorphImage(morphImage, true);
        
        return morphImage;
    }

    public void startTimer() {
        time0 = System.currentTimeMillis();
        timeSub = 0;
        timeSub0 = 0;
    }

    public void stopTimer() {
        elapsedTime = System.currentTimeMillis() - time0 - timeSub;
    }
    public void freezeTimer() {
        timeSub0 = System.currentTimeMillis();
    }

    public void unFreezeTimer() {
        timeSub += System.currentTimeMillis() - timeSub0;
    }

    public void updateTimer() {
    	
    	if(!displayOff){
    		long t = System.currentTimeMillis() - time0;
    		t -= timeSub;
    		timerLabelSeconds.setText(Long.toString(t) + " ms");
    	}
    }

    public void updateDoneLabel() {
    	if(!displayOff)
    		timerLabel.setText("Done morphing images in: ");
    }

    public void updateMorphImage(BufferedImage image, boolean hiQuality) {
    	if(!displayOff){
    		BufferedImage displayImage =  ImageUtils.getScaledInstance(image, morphImageSize.width, morphImageSize.height, RenderingHints.VALUE_INTERPOLATION_BILINEAR, hiQuality);
    		morphIcon = new ImageIcon(displayImage);
    		morphLabel.setIcon(morphIcon);
    	}
    }

    static void Usage() {
        System.err.println("Usage: MorphImg \n" +
        		"[-p <n> ] switch to parallel mode with n number of threads\n" + 
        		"[-u <n> ] display frequency (default 8)\n" + 
        		"[-f <n> ] fast stepping (default 4)\n" + 
        		"[-t ] turn off display\n" + 
        		"[-i image1 image2 ]\n");
        
        System.exit(0);
    }
    
    public static void main(String[] args)
    {
        JFrame frame ; 
        
        String title = "Serial Morpher";
        
        // Default image file names
        String fromImageStr = "from.jpg";
        String toImageStr = "to.jpg";

        String file_extension = "_sm"; //serial morph
       
        int i = 0;
        int numThreads = 1 ;
        int updateSteps = 8;
        int fast_stepping = 4;
        
        while (i < args.length && args[i].startsWith("-")) 
        {
            String arg = args[i++];

            if (arg.equals("-i")) {
                if (i > (args.length-2)){
                    System.err.println("-i Needs 2 file arguments");
                    Usage();
                }
                // 2 input files
                fromImageStr = args[i++];                 
                               
                toImageStr = args[i++];

            }
            // Parallel mode
             else if (arg.equals("-p")) {
                parallelMode = true;
                try{ numThreads =( Integer.parseInt(args[i++]));
                } catch (NumberFormatException Exc){
                    System.err.println("-p needs an int");
                    Usage();
                }
                Morph.setNumThreads(numThreads);
                title = "Parallel Morpher";
		// The output file will have the extension "_pm" added
                file_extension = "_pm"; //parallel morph
                
            } //display frequency for update steps  
             else if (arg.equals("-u")) {
                try{ updateSteps = ( Integer.parseInt(args[i++]));
                	Morph.setUpdateSteps(updateSteps);
                } catch (NumberFormatException Exc){
                	System.err.println("-u needs an int");
                	Usage();
                }
            }
             else if (arg.equals("-f")) {
                try{ fast_stepping = ( Integer.parseInt(args[i++]));
                	Morph.setFastStepping(fast_stepping);
                } catch (NumberFormatException Exc){
                	System.err.println("-f needs an int");
                	Usage();
                }
            // shuts off display
            } else if (arg.equals("-t")) {
            	displayOff = true;
            	System.out.println("Display turned off!");
                Morph.setDisplayOff(true);
            }
            else{
                System.err.println("Unknown option " + arg);
                Usage();
            }
        }
        if (i < args.length) {
            System.err.println("Unknown option " + args[i]);
            Usage();
        }
 
        File fromFile = new File(fromImageStr);
        File toFile = new File(toImageStr);
        assert(fromFile.exists() && fromFile.isFile());
        assert(toFile.exists() && toFile.isFile());

        //Read images
        BufferedImage fromImage = null; 
        BufferedImage toImage = null;
        try {
            fromImage = ImageIO.read(fromFile);
            toImage = ImageIO.read(toFile);
        } catch(IOException e) {
            e.printStackTrace();
        }

        //Initiate app and morphing
        MorphImg ma = new MorphImg(fromImage, toImage);
            
        if(!displayOff){
        	frame = new JFrame(title);
        	frame.setContentPane(ma);
        	frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        	frame.pack();
        	frame.setVisible(true);
        	frame.setSize(650, 650);
        }
        
        BufferedImage morphImage = ma.morph();
        	
        File output = new File(toImageStr + file_extension);
        
        try {
			ImageIO.write(morphImage, "JPG", output);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	
		System.out.println("Elapsed Time (ms): " +  Long.toString(elapsedTime));
	
		if(!displayOff){
			try{
			 // last 2 sec before self destruction
		       Thread.sleep(2000); 
			}
			catch(Exception e)
			{
				System.out.println(e);
			}
		}
		
		System.exit(0);
    }
}
